home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d18 / kruse_11.arc / INDEXFIL.PAS < prev    next >
Pascal/Delphi Source File  |  1990-11-30  |  35KB  |  938 lines

  1. {outline of declaration of subprograms:
  2.  
  3.  1.     program IndexText(InText, InIndex, NewIndex, OutIndex, HashFile,
  4.                           NewHashFile, input, output);     (main program)
  5.  2.         function Lt(u, v: word):  Boolean;
  6.  3.         procedure ReadWord(var f: text;  var w: word);
  7.  4.         procedure WriteWord(var f: text; w: word);
  8.  4a.        built in CPU time function   clock;
  9.  
  10.  5.         procedure SplitWords;                       (phase 1)
  11.  5a.            function FindFile(ch: char): filecode;
  12.  6.             function HashAddress(w: word):  hashentry;
  13.  7.             procedure Initialize;
  14.  8.             procedure GetWord;
  15.  8a.                procedure TellUserPage;
  16.  9.                 procedure GetChar(var ch: char);
  17. 10.                 procedure AddChar(ch: char);
  18. 11.             procedure Conclude;
  19.  
  20. 12.         procedure ClassifyWords;                    (phase 2)
  21. 13.             procedure BuildTree(var root: pointer; ch: char);
  22. 15.                 function Power2(c: integer): level;
  23.                   (the next three procedures are written in line.)
  24. 14.                 procedure Insert(p: pointer);
  25. 16.                 procedure FindRoot;
  26. 17.                 procedure ConnectSubtrees;
  27. 18.                 procedure GetNode(var p: pointer; ch: char);
  28. 19.             procedure Process(r: reference);
  29. 20.                 procedure UpdateNode(p: pointer; r: reference);
  30. 21.                 procedure NewWord(var p: pointer; r: reference);
  31. 22.                 procedure InsertTree(r, p: pointer);
  32. 23.             procedure OutputTree(p: pointer);
  33. 24.                 procedure PutNode(p: pointer);
  34. }
  35.  
  36.  
  37.  
  38. program IndexText(InText, InIndex, NewIndex, HashFile, NewHashFile,
  39.                   input, output);
  40.  
  41. {Produces word counts and list of references for the document file 
  42.  InText. Uses the master word list in file InIndex, if provided. Output word
  43.  list for new text goes to file NewIndex. HashFile contains the common words
  44.  to be ignored. If not specified, it is created on output, containing the
  45.  words so flagged by the user.}
  46. {This implementation uses only phases 1 and 2. A smaller array of text files
  47.  is also used, as specified in the exercise section.}
  48.  
  49. const
  50.   maxwd         =   20;             {More letters in word will be ignored.}
  51.   minwd         =    1;                    {Shorter words will be ignored.}
  52.   hashsize      = 2003;                                 {should be a prime}
  53.   linesperpage  =   66;                {assumes standard spacing and paper}
  54.   maxheight     =   20;               {for building binary tree in phase 2}
  55.   A             =  'A';
  56.   Z             =  'Z';
  57.   hyphen        =  '-';
  58.   blank         =  ' ';
  59.   apostrophe    = '''';               {requires two `'s  to represent one}
  60.   underscore    =  '_';
  61.   ordbackspace  =    8;            {ASCII control character for backspace}
  62.   ordformfeed   =   12;             {ASCII control character for new page}
  63.   changecase    =   32;    {ASCII difference between upper and lower case}
  64.   nfiles        =    8;  {number of temporary files for unprocessed words}
  65.   MaxRowLength  =   130;                 {maximum length of output records}
  66.  
  67. type
  68.   word          =  packed array[1..maxwd] of char;
  69.   reference     =  record
  70.                       wd:   word;
  71.                       pg:   integer;               {count or page number}
  72.                    end;
  73.   fileref       =  file of reference;              {used for local files}
  74.   letter        =  A..Z;
  75.   hashentry     =  1..hashsize;
  76.   filecode      =  1..nfiles;
  77.  
  78. var
  79.   InText,                                     {document being processed}
  80.   InIndex,                                            {master word list}
  81.   NewIndex,                              {word list of current document}
  82.   HashFile,
  83.   NewHashFile:      text;
  84.   RefFile:      array[filecode] of fileref; {local files used for auxilary
  85.                                storage of words from phase 1 to phase 2:
  86.                 Normally, a separate file exist for each initial letter,
  87.         this version uses nfiles files due operating system constraints.}
  88.   blankword:    word;                           {will contain all blanks}
  89.  
  90. {The next two variables were originally declared in procedure SplitWords,
  91.  they have been moved to this level in order to access them globally.}
  92.   outcount:     array[filecode] of integer;    {counters for word  files}
  93.   wordcount:    integer;                 {count of all words in the text}
  94.  
  95.   intextname,
  96.   inlistname,
  97.   newlistname,
  98.   newhashname:  word;                    {used to get filename from user}
  99.   lastletter:   array[filecode] of letter;     {last letter in each file}
  100.   PresentTime,
  101.   StartTime:    integer;                         {used to track CPU time}
  102.   RowLength:    integer;   {ensures records will not exceed MaxRowLength}
  103.  
  104.  
  105. function Lt( u, v: word): Boolean;
  106. {Determains if word u precedes word v lexicographically.}
  107. begin
  108.   Lt := (u < v)
  109. end;
  110.  
  111. procedure ReadWord( var F: text;  var w: word);
  112. {Reads word w from text file F.  Assumes not at end of file.}
  113. {Uses packed array, replace using a loop if your system does not 
  114.  support packed arrays. }
  115. begin                           {procedure ReadWord}
  116.   read(F, w)
  117. end;                            {procedure ReadWord}
  118.  
  119. procedure WriteWord( var F: text; w: word);
  120. {Writes word w to text file F}
  121. {Uses packed array, replace using a loop if your system does not 
  122.  support packed arrays. }
  123. begin                           {procedure WriteWord}
  124.   write(F, w)
  125. end;                            {procedure WriteWord}
  126.  
  127. procedure SetTimer;     {Call once at beginning of program execution.}
  128. {Finds the CPU time when called, and keeps in variables for reference.}
  129. {System dependent procedure.}
  130. begin
  131.   PresentTime := clock;
  132.   StartTime := PresentTime;
  133. end;
  134.  
  135. function TotalTime:  real;
  136. {Returns the total CPU time, in seconds, since call to SetTimer.}
  137. {System dependent procedure.}
  138. begin
  139.   TotalTime := (clock - StartTime) / 1000.0;
  140. end;
  141.  
  142. function ElapsedTime:  real;
  143. {Returns elapsed CPU time since last call to function ElapsedTime,
  144.  or call to SetTimer, whichever is more recent.}
  145. {System dependent procedure.}
  146. var r: integer;
  147. begin
  148.   r := clock;
  149.   ElapsedTime := (r - PresentTime) / 1000.0;
  150.   PresentTime := r;
  151. end;
  152.  
  153.  
  154.  
  155. procedure SplitWords;
  156. {sets up hash table, reads text, and divides into nfiles word lists}
  157.  
  158. var
  159.   hash:       array[hashentry] of word;              {hash table}
  160.   pagecount:  integer;                 {keeps the current page number}
  161.   addpage:    integer;       {amount to increase pagecount after word}
  162.   linecount:  integer;                     {lines on the current page}
  163.   w:          word;                   {word currently being processed}
  164.   x:          hashentry;             {location of w, if in hash table}
  165.   endinput:   Boolean;   {true if and only if input has all been read}
  166.   code:       filecode;                {into which file does word go?}
  167.  
  168. {The following variables are kept for use in procedure GetWord, and for
  169.  efficiency are set up only once in procedure Initialize:}
  170.   backspace,
  171.   formfeed:   char;
  172.   alphabet,                           {letters only - to start a word}
  173.   contchar:   set of char;     {other characters ok in middle of word}
  174.  
  175.  
  176.   function  FindFile( ch:  letter):  filecode;
  177.   {Uses binary decision tree to select one of nfiles = 8 files depending
  178.    on the letter ch.  These letters must be the same as those in the
  179.    global array  lastletter  .}
  180.   begin                           {function FindFile}
  181.     if            ch < 'M' then
  182.       if          ch < 'E' then
  183.         if        ch < 'C' then  FindFile := 1
  184.                            else  FindFile := 2
  185.       else if     ch < 'H' then  FindFile := 3
  186.                            else  FindFile := 4
  187.     else if       ch < 'S' then
  188.       if          ch < 'P' then  FindFile := 5
  189.                            else  FindFile := 6
  190.       else if     ch < 'T' then  FindFile := 7
  191.                            else  FindFile := 8
  192.   end;                            {function FindFile}
  193.  
  194.  
  195.   function HashAddress(w: word): hashentry;   {modified from textbook}
  196.   {calculates the location in hash table of word w, or, if not there,
  197.    returns pointing to the blank word where w should go}
  198.  
  199.   var
  200.     x,                            {calculated location}
  201.     inc:     integer;             {increment for open addressing}
  202.   begin                           {function HashAddress}
  203.     x := abs(ord(w[1])*ord(w[2])+ord(w[4])+ord(w[6])) mod hashsize + 1;
  204. {Hash function assumes long word length. For short word machines
  205.  we must ensure that the result is non-negative, and worry about overflow.}
  206.  
  207.     if (hash[x] <> w) and (hash[x] <> blankword) then
  208.       begin
  209.         inc   := (abs(ord(w[3])-95) mod 29);
  210.                   {A key dependent increment is used to avoid clustering.}
  211.         repeat
  212.           inc := inc + 1;
  213.           if inc > hashsize then
  214.             writeln(w,' causes hash table to become full, infinite loop.');
  215.           x := x + inc;
  216.           if x > hashsize then x := x - hashsize;
  217.         until (w =  hash[x])  or  (blankword = hash[x])
  218.       end;
  219.     HashAddress := x
  220.   end;                            {function HashAddress}
  221.  
  222.  
  223.   procedure Initialize;
  224.   {sets up constant-valued sets for use in GetWord. Opens the text file
  225.    and initializes various counters. Opens file holding hash table (if any),
  226.    and reads or otherwise initializes table}
  227.   var
  228.     i:         integer;          {general purpose loop control}
  229.  
  230.   begin                           {procedure Initialize}
  231.     backspace:= chr(ordbackspace);
  232.     formfeed := chr(ordformfeed); {initialize ASCII control characters}
  233.     alphabet := ['A'..'Z', 'a'..'z'];      {letters only, to start a word}
  234.     contchar := [hyphen, apostrophe, backspace, underscore];
  235.                                 {characters which will not terminate word}
  236.     for i := 1 to maxwd do
  237.       blankword[i] := blank;
  238.  
  239.     write('Name of input text file?');
  240.     ReadWord(input, intextname); readln;
  241.     open(InText, intextname, readonly);
  242.     reset(InText);
  243.     endinput := eof(InText);
  244.  
  245.     repeat
  246.       write( 'What is the page number on which the text begins?');
  247.       readln(pagecount);
  248.       if pagecount < 0 then
  249.         writeln('Must be a non-negative integer.')
  250.     until pagecount >= 0;
  251.     linecount := 0;
  252.     addpage   := 0;
  253.     wordcount := 0;
  254.  
  255.     for i := 1 to nfiles do
  256.     begin
  257.       rewrite( RefFile[i] );
  258.       outcount[i] := 0
  259.     end;
  260.     lastletter[1] := 'B';
  261.     lastletter[2] := 'D';
  262.     lastletter[3] := 'G';
  263.     lastletter[4] := 'L';
  264.     lastletter[5] := 'O';
  265.     lastletter[6] := 'R';
  266.     lastletter[7] := 'S';
  267.     lastletter[8] := 'Z';
  268.  
  269.     reset(HashFile);   {assumes HASHFILE.DAT is in current directory}
  270.     if eof(HashFile) then
  271.     begin     {There is no previous table; initialize the table to all blanks.}
  272.       writeln('Cannot open file for hash table. Creating a new table.');
  273.       for i := 1 to hashsize do
  274.         hash[i] := blankword
  275.     end
  276.     else begin                 {Retrieve the previous hash table.}
  277.       i := 0;
  278.       repeat
  279.         i := i + 1;
  280.         hash[i] := HashFile^;
  281.         get(HashFile)
  282.       until eof(HashFile) or (i >= hashsize);
  283.       if (not eof(HashFile)) or (i <> hashsize) then
  284.         writeln('Error in reading hash table. Incorrect number of entries.')
  285.     end
  286. end;                                        {procedure Initialize}
  287.  
  288.  
  289.  
  290.   procedure GetWord( var  w: word);
  291.   {Gets words from input file InText, and returns only words
  292.    at least minwd characters long.  Parameter endinput becomes
  293.    true if and only if the end of InText is reached with no word to return.
  294.    the procedure also updates global variables wordcount and linecount,
  295.    updates the global variable pagecount after each linesperpage cr's,
  296.    or after each formfeed, whichever comes first, and
  297.    uses the sets alphabet and contchar and various character constants.}
  298.  
  299.   label 1;           {used by GetChar to exit procedure upon eof(InText)}
  300.  
  301.   var  c:      0..maxwd;                    {count of characters in word}
  302.        ch:     char;                      {character currently processed}
  303.        endln:  Boolean;                           {at the end of a line?}
  304.  
  305.  
  306.   procedure TellUserPage;         {keep the user informed of progress}
  307.   var   i: integer;
  308.   begin
  309.     i := pagecount + addpage;
  310.     writeln('At page', i:4, ' word count is', wordcount:7)
  311.   end;
  312.  
  313.  
  314.   procedure GetChar(var ch: char);
  315.   {gets a character from input text into ch; checks for eof; updates
  316.    page count and line count}
  317.  
  318.   begin                                                {procedure GetChar}
  319.     if eof(InText) then
  320.       if c >= minwd then
  321.         ch := '.'              {special character to end the current word}
  322.       else begin                         {no word to return; set endinput}
  323.         endinput := true;
  324.         goto 1                                        {exit from GetWord.}
  325.       end
  326.     else begin                   {not end of file: process next character}
  327.       while InText^ in [underscore, backspace] do
  328.         get( InText);
  329.       ch := InText^;
  330.       endln := eoln(InText);
  331.       get(InText);
  332.       if endln then
  333.       begin
  334.         linecount := linecount + 1;
  335.         if linecount >= linesperpage then
  336.           begin
  337.             addpage := addpage + 1;
  338.             linecount := 0;
  339.             TellUserPage
  340.           end
  341.       end;
  342.       if ch = formfeed then
  343.         begin
  344.           addpage := addpage + 1;
  345.           linecount := 0;
  346.           TellUserPage;
  347.           endln := true;            {Treat formfeed like end of line.}
  348.           ch := blank
  349.         end
  350.     end
  351.   end;                                            {procedure GetChar}
  352.  
  353.  
  354.   procedure AddChar(ch: char);
  355.   {adds given character to word, if possible}
  356.   begin                           {procedure AddChar}
  357.     if c < maxwd then
  358.     begin
  359.       c := c + 1;
  360.       w[c] := ch
  361.     end
  362.   end;                            {procedure AddChar}
  363.  
  364.  
  365.   begin                           {procedure GetWord}
  366.     repeat                {until current word is at least minwd chars long}
  367.       c := 0;
  368.       repeat
  369.         GetChar(ch)               {Find a letter which will start the word.}
  370.       until ch in alphabet;
  371.       pagecount := pagecount + addpage;
  372.       addpage := 0;
  373.       if ch in ['a'..'z'] then       {translate first letter to upper case.}
  374.         ch := chr(ord(ch) - changecase); {assumes ASCII ordering of letters}
  375.       AddChar(ch);                          {put first letter into the word}
  376.       GetChar(ch);
  377.       while (ch in alphabet) or (ch in contchar) do
  378.         if ch in alphabet then                {add letters directly to word}
  379.         begin                                            {processing letter}
  380.           AddChar(ch);
  381.           GetChar(ch)
  382.         end                                              {processing letter}
  383.         else if ch = hyphen then
  384.         begin                                            {processing hyphen}
  385.           GetChar(ch);                       {Find what comes after hyphen.}
  386.           if endln then
  387.             while ch = ' ' do
  388.               GetChar(ch)       {Delete both the hyphen and the end of line}
  389.           else if ch = hyphen then      {Two hyphens form a dash; ends word}
  390.             ch := blank                 {Use a blank to terminate the word.}
  391.           else if ch in alphabet then
  392.             AddChar(hyphen)                  {Include other hyphens in word}
  393.           else      {nothing}
  394.         end                                              {processing hyphen}
  395.         else if ch = apostrophe then
  396.         begin                                        {processing apostrophe}
  397.           GetChar(ch);
  398.           if ch = 's' then              {Delete  `'s'   at end of word only}
  399.           begin
  400.             GetChar(ch);
  401.             if ch in contchar then
  402.             begin
  403.               AddChar(apostrophe);
  404.               AddChar('s')
  405.             end
  406.           end
  407.           else if ch in alphabet then
  408.              AddChar(apostrophe)                      {Allow contractions.}
  409.         end                                         {processing apostrophe}
  410.         else         {Remaining possibilities are backspace and underscore.}
  411.           GetChar(ch);                           {Delete these characters.}
  412.       {While loop on continuing characters ends here.}
  413.       wordcount := wordcount + 1
  414.     until c >= minwd;                              {Skip over short words.}
  415.  
  416.     while c < maxwd do                                  {Fill with blanks.}
  417.     begin
  418.       c := c + 1;
  419.       w[c] := blank
  420.     end;
  421.   1:      {When end of file occurs, program will exit to here from GetChar}
  422.   end;                                                  {procedure GetWord}
  423.  
  424.  
  425.  
  426. procedure Conclude;
  427. {Writes out counts of various word lists. For some systems, it is 
  428.  necessary to close files, which should be done here.}
  429. var
  430.   i: integer;                                                 {loop index}
  431. begin                                                 {procedure Conclude}
  432.   writeln('The total number of words read in is ', wordcount:7);
  433.   writeln;
  434.   writeln('The number of words to process further in the next stage,');
  435.   writeln('in each temporary file, is below.');
  436.   writeln('     a-b     c-d     e-g     h-l     m-o     p-r      s      t-z');
  437.   for i := 1 to nfiles do
  438.     write(outcount[i]:8);
  439.   writeln;
  440.   writeln
  441. end;                                                  {procedure Conclude}
  442.  
  443.  
  444.  
  445. begin                                          {procedure  SplitWords}
  446.   Initialize;                   {sets up files, hash table, constants}
  447.   GetWord(w);                       {obtain a single word from InText}
  448.   while not endinput do
  449.   begin
  450.     x := HashAddress(w);
  451.     if w <> hash[x] then
  452.     begin
  453.       code := FindFile( w[1] );
  454.       outcount[code] := outcount[code] + 1;
  455.       with RefFile[code]^ do
  456.       begin
  457.         wd := w;
  458.         pg := pagecount
  459.       end;
  460.       Put(RefFile[code])
  461.     end;
  462.     GetWord(w)
  463.   end;
  464.   Conclude                           {writes word counts to output.}
  465. end;                                          {procedure SplitWords}
  466.  
  467.  
  468.  
  469.  
  470.  
  471. {start of phase 2}
  472.  
  473. procedure ClassifyWords;
  474. {For each letter of the alphabet, the procedure reads in a list of
  475.  words from InIndex, builds them into a binary tree, supplements it
  476.  with entries from RefFile, and writes the result to files NewIndex
  477.  and NewHashFile.}
  478.  
  479. type
  480.   wordtype  = (hash, count, page, question, index); {ways to process a word}
  481.   pointref  = ^reflist;
  482.   reflist   = record                            {list of references}
  483.                 pg:   integer;
  484.                 next: pointref
  485.               end;
  486.   pointer   = ^node;
  487.   node      = record                    {vertex of the binary tree}
  488.                 wd:       word;
  489.                 left,
  490.                 right:    pointer;
  491.                 ct:       integer;
  492.                 case kind:  wordtype of
  493.                   hash, count:
  494.                     ();
  495.                   page, question, index:
  496.                     (ref:   pointref)
  497.               end;
  498. var
  499.   root:       pointer;                    {root of binary tree}
  500.   code:       filecode;          {loop through temporary files}
  501.   endlist:    Boolean;             {at end of input word list?}
  502.   i:          integer;          {general purpose loop variable}
  503.  
  504.  
  505.  
  506. procedure BuildTree(var root: pointer;  code: filecode);
  507.  
  508. {Reads a sequential file in alphabetical order, and converts it into
  509.  a binary search tree. Stops reading when the first letter of word
  510.  is after lastletter[code].
  511.  const  maxheight = 20  (in main program) allows 512k entries.}
  512.  
  513. {This procedure was modified slightly to fit the needs of this application.
  514.  The parameters of GetNode now include a character ch, which has also
  515.  been introduced as a local variable.}
  516.  
  517. type
  518.   level = -1 .. maxheight;      {number of steps above leaves}
  519.  
  520. var
  521.   lastnode:  array[level] of pointer;   {contains pointer to
  522.                          last node processed on each level}
  523.   counter:   integer;           {number of nodes read in so far}
  524.   p:         pointer;           {p^ is present input node}
  525.   lev:       level;             {level of p^}
  526.   ch:        char;              {will be last letter to be processed.}
  527.  
  528.  
  529.   function Power2(c:  integer): level;
  530.   {finds the highest power of 2 which divides c}
  531.   var
  532.     lev:   level;
  533.   begin                           {function Power2}
  534.     lev := 0;
  535.     while not odd(c) do
  536.     begin
  537.       c := c div 2;
  538.       lev := lev + 1
  539.     end;
  540.     Power2 := lev
  541.   end;                            {function Power2}
  542.  
  543.  
  544.   procedure Insert(p: pointer);
  545.   {Inserts p^ as rightmost node of a partial binary search tree.}
  546.   var
  547.     lev:       level;      {level of p^}
  548.   begin                    {Procedure Insert}
  549.     lev      := Power2(counter);
  550.     p^.right := nil;
  551.     p^.left  := lastnode[lev - 1];
  552.     lastnode[lev] := p;
  553.     if lastnode[lev + 1] <> nil then
  554.       with lastnode[lev + 1]^ do
  555.       if right = nil then right := p
  556.   end;                     {Procedure Insert}
  557.  
  558.  
  559.   procedure FindRoot;
  560.   var
  561.     lev:    level;
  562.   begin                    {Procedure FindRoot}
  563.     if counter = 0 then
  564.       root := nil          {Tree is empty.}
  565.     else begin             {Non-empty tree}
  566.       lev := maxheight;    {Find the highest occupied level; it gives the root}
  567.       while lastnode[lev] = nil do lev := lev - 1;
  568.       root := lastnode[lev]
  569.     end
  570.   end;                     {Procedure FindRoot}
  571.  
  572.  
  573.   procedure ConnectSubtrees;
  574.   var
  575.     p:         pointer;
  576.     lev:       level;
  577.     s:         level;
  578.   begin                    {Procedure ConnectSubtrees}
  579.     lev := maxheight;
  580.     while (lastnode[lev] = nil) and (lev > 1) do
  581.       lev := lev - 1;      {Find the highest node:  root}
  582.     while lev > 1 do       {Nodes on levels 1 and 0 are already OK}
  583.       with lastnode[lev]^ do
  584.       if right <> nil then
  585.         lev := lev - 1     {Search down for the highest dangling node}
  586.       else begin           {Case:  right subtree is undefined.}
  587.         p := left;         {Find the highest entry in lastnode that}
  588.         s := lev - 1;                     {is not in the left subtree.}
  589.         repeat
  590.           p := p^.right;
  591.           s := s - 1
  592.         until (p = nil) or (p <> lastnode[s]);
  593.         right := lastnode[s];
  594.         lev := s           {Nodes on levels between lev and s are on the left.}
  595.       end                  {Connecting dangling subtrees}
  596.   end;                     {Procedure  ConnectSubtrees}
  597.  
  598.  
  599.   procedure GetNode( var p: pointer;  ch:  char);
  600.   {reads a word from file  InIndex  and sets node correspondingly}
  601.   {returns p = nil at eof or when next word starts later than code.}
  602.   var
  603.     wordcode:  char;                {letter indicating type of word}
  604.  
  605.   begin                                          {procedure GetNode}
  606.     while InIndex^ = '&' do         {ignore lines starting with '&'}
  607.       readln(InIndex);
  608.     while (not eof(InIndex)) and (InIndex^ = blank) do
  609.       get(InIndex);                        {Skip all leading blanks}
  610.     if endlist or eof(InIndex) then
  611.       p := nil
  612.     else if InIndex^ > ch then
  613.       p := nil
  614.    else begin
  615.       new(p);
  616.       with p^ do begin
  617.         ReadWord(InIndex, wd);
  618.         while (InIndex^ = ' ') and (not eoln(InIndex)) do
  619.           get(InIndex);
  620.         read(InIndex, wordcode);
  621.         ct := 0;
  622.         if wordcode in ['C', 'H','I','P','?'] then
  623.         case wordcode of
  624.           'C':  kind := count;
  625.  
  626.           'H':  begin
  627.                   writeln('Warning: The input word list contains ', wd);
  628.                   writeln('         which belongs in the hash table.');
  629.                   kind := hash
  630.                 end;
  631.  
  632.           'I':  begin kind := index;     ref := nil  end;
  633.           'P':  begin kind := page;      ref := nil  end;
  634.           '?':  begin
  635.                   writeln('Questionable word: ', wd, ' in word list.');
  636.                   write('New category (P, I, C, H, ?');
  637.                   repeat
  638.                     readln(wordcode);
  639.                     if wordcode > 'Z' then 
  640.                       wordcode := chr(ord(wordcode) - changecase)
  641.                   until wordcode in ['H','C','P','?','I'];
  642.                   case wordcode of
  643.                     'H':       kind := hash;
  644.                     'C':       kind := count;
  645.                     'P', ' ':  kind := page;
  646.                     '?':       kind := question;
  647.                     'I':       kind := index
  648.                   end;
  649.                   if kind in [page, question, index] then ref := nil
  650.                 end
  651.           end
  652.           else
  653.             writeln('Erroneous word code ', wordcode, ' in file InIndex.')
  654.       end;                           {with statement setting up the node}
  655.       readln(InIndex);          {Advance to the start of the next entry.}
  656.       endlist := eof(InIndex)
  657.     end
  658.   end;                                                {procedure GetNode}
  659.  
  660.  
  661. begin                           {procedure BuildTree}
  662.   for lev := -1 to maxheight do  lastnode[lev] := nil;
  663.   counter := 0;
  664.   ch := lastletter[code];
  665.   GetNode(p, ch);
  666.   while p <> nil do
  667.   begin
  668.     counter  := counter + 1;
  669.     Insert(p);
  670.     GetNode(p, ch)
  671.   end;                          {reading and processing input}
  672.   FindRoot;
  673.   ConnectSubtrees
  674. end;                            {procedure  BuildTree}
  675.  
  676.  
  677.  
  678. procedure Process( r: reference);
  679. {Takes the word and page reference r, and updates the binary tree.}
  680. var
  681.   p:          pointer;                      {trace through the tree}
  682.   found:      Boolean;                    {Is the word in the tree?}
  683.  
  684.  
  685. procedure UpdateNode( p:  pointer;  r: reference);
  686. {uses reference r to update information in node p^}
  687.  
  688. var
  689.   q:     pointref;              {used to add reference to list}
  690. begin                                    {procedure UpdateNode}
  691.   with p^ do
  692.   begin
  693.     ct := ct + 1;
  694.     if  kind  in  [page, question, index] then
  695.       if ref = nil then
  696.       begin
  697.         new(ref);
  698.         ref^.pg   := r.pg;
  699.         ref^.next := nil
  700.       end
  701.       else if ref^.pg <> r.pg then
  702.       begin                     {add the new reference to list.}
  703.         new(q);
  704.         q^.pg   := r.pg;
  705.         q^.next := ref;
  706.         ref     := q
  707.       end
  708.   end                           {with statement to update tree}
  709. end;                            {procedure UpdateNode}
  710.  
  711.  
  712. procedure NewWord(var p: pointer;  r: reference);
  713. {Creates a node for the first occurrence of a new reference r. A
  714.  pointer to the new node is returned in p.}
  715.  
  716. var
  717.   response:       char;                {answer received from user}
  718. begin                                          {procedure NewWord}
  719.   new(p);
  720.   with p^ do
  721.   begin
  722.     wd    := r.wd;
  723.     left  := nil;
  724.     right := nil;
  725.     ct    := 1;
  726.  
  727.     kind  := question;
  728.     repeat                           {ask user what kind of word}
  729.       WriteWord(output, wd);
  730.       write('  is (H, C, P, ?, I)?');
  731.       readln(response);
  732.       if response > 'Z' then response := chr(ord(response) - changecase)
  733.     until response in ['H', 'C', 'P', ' ', '?', 'I'];
  734.     case response of
  735.       'H':         kind := hash;
  736.       'C':         kind := count;
  737.       'P', ' ':    kind := page;
  738.       '?':         begin
  739.                      kind := question;
  740.                      writeln('First occurence of word is on page', r.pg:5, '.')
  741.                    end;
  742.       'I':         kind := index
  743.     end;  {case statement}
  744.     if kind in [page, question, index] then
  745.     begin
  746.       new(ref);
  747.       ref^.pg   := r.pg;
  748.       ref^.next := nil;
  749.     end
  750.   end                                          {with statement}
  751. end;                                        {procedure NewWord}
  752.  
  753.  
  754. procedure InsertTree(r, p: pointer);
  755. {adds a node p^ to the tree with root r^; requires that r <> nil
  756.  and p^ not be in the tree; proceeds by recursion}
  757.  
  758. begin                           {procedure InsertTree}
  759.   if Lt(p^.wd, r^.wd) then
  760.     if r^.left = nil then r^.left := p
  761.     else InsertTree(r^.left, p)
  762.   else
  763.     if r^.right = nil then r^.right := p
  764.     else InsertTree(r^.right, p)
  765. end;                            {procedure InsertTree}
  766.  
  767.  
  768. begin                                        {procedure Process}
  769.   if root = nil then                  {The tree might be empty.}
  770.     NewWord(root, r)
  771.   else begin                            {case of non-empty tree}
  772.     p := root;                            {Begin a tree search.}
  773.     found := false;
  774.     repeat
  775.       if r.wd = p^.wd then
  776.         found := true
  777.       else if Lt(r.wd,p^.wd) then
  778.         p := p^.left
  779.       else
  780.         p := p^.right
  781.     until found or (p = nil);
  782.  
  783.     if found then UpdateNode(p, r)
  784.     else begin                  {p^ was not found: add it to the tree.}
  785.       NewWord(p, r);
  786.       InsertTree(root, p)
  787.     end
  788.   end
  789. end;                                               {procedure Process}
  790.  
  791.  
  792. procedure OutputTree( p: pointer);
  793. {traverses the tree for which p^ is the root in inorder}
  794.  
  795. procedure PutNode( p:  pointer);
  796. {Puts the information in p^ into the file NewIndex.}
  797.  
  798. var
  799.   q:        pointref;           {used to traverse list of references}
  800.   response: char;
  801. begin                                             {procedure PutNode}
  802.   with p^ do  if ct > 0 then
  803.   begin                         {Otherwise, word is not in document.}
  804.     if kind <> hash then
  805.       WriteWord(NewIndex, wd);
  806.     case kind of
  807.       hash:      begin      {new hash entries written to NewHashFile}
  808.                    WriteWord(NewHashFile, wd);
  809.                    writeln(NewHashFile)
  810.                  end;
  811.       count:     write(NewIndex, 'C');
  812.       page:      write(NewIndex, 'P');
  813.       index:     write(NewIndex, 'I');
  814.       question:
  815.         begin
  816.           repeat                      {ask user what kind of word}
  817.             WriteWord(output, wd);
  818.             write('  is questionable.  Change to (h, c, p, ?, i)?');
  819.             readln(response);
  820.             if response > 'Z' then response := chr(ord(response) - changecase)
  821.           until response in ['H', 'C', 'P',' ', '?', 'I'];
  822.           case response of
  823.             'H':       begin kind := hash;   write(NewIndex, 'H') end;
  824.             'C':       begin kind := count;  write(NewIndex, 'C') end;
  825.             'P', ' ':  begin kind := page;   write(NewIndex, 'P') end;
  826.             'I':       begin kind := index;  write(NewIndex, 'I') end;
  827.             '?':       begin
  828.                          kind := question;
  829.                          write(NewIndex, '?');
  830.                          write('The word appears on the following page(s)');
  831.                          q := ref;
  832.                          repeat
  833.                            write(q^.pg:6);
  834.                            q := q^.next
  835.                          until q = nil;
  836.                          writeln
  837.                        end                  {case of questionable word}
  838.           end                                 {case response statement}
  839.         end                            {treating new or question words}
  840.     end;                                {case kind statement}
  841.     if kind <> hash then
  842.       write(NewIndex, ct:6);
  843.     if kind in [page, question, index] then
  844.     begin
  845.       q := ref;
  846.       RowLength := 28;   {ensures that record will not exceed desired length}
  847.       repeat
  848.         if RowLength > (MaxRowLength - 4) then
  849.           begin
  850.             writeln(NewIndex);
  851.             write(NewIndex,'&  ');     {& indicates continuation of index}
  852.             RowLength := 3
  853.           end;
  854.         write( NewIndex, q^.pg:4);
  855.         q := q^.next;
  856.         RowLength := RowLength + 4
  857.       until q = nil;
  858.     end;
  859.     if kind <> hash then
  860.       writeln( NewIndex )
  861.   end                           {with statement and if statement}
  862. end;                            {procedure PutNode}
  863.  
  864.  
  865. begin                                       {procedure OutputTree}
  866.   if p <> nil then
  867.   with p^ do
  868.   begin
  869.     OutputTree(left);                  {Traverse the left subtree}
  870.     PutNode(p);
  871.     OutputTree(right);                {Traverse the right subtree}
  872.     dispose(p)
  873.   end
  874. end;                                        {procedure OutputTree}
  875.  
  876.  
  877.  
  878. begin                           {procedure ClassifyWords}
  879.  
  880.   write('Name of input word list ?');
  881.   ReadWord(input, inlistname);
  882.   readln;
  883.   open(InIndex, inlistname, readonly);
  884.   reset(InIndex);
  885.   endlist := eof(InIndex);
  886.  
  887.   write('Name of output word list ?');
  888.   ReadWord(input, newlistname);
  889.   readln;
  890.   open(NewIndex, newlistname);
  891.   rewrite(NewIndex);
  892.  
  893.   writeln('Rewriting NEWHASHFILE.DAT to contain all new hash words.');
  894.   rewrite(NewHashFile);
  895.  
  896.   writeln('At the appearance of each word, indicate its disposition:');
  897.   writeln('  H -  Place this word in hash table and count its frequency.');
  898.   writeln('  C -  Count how many times this word appears.');
  899.   writeln('  P -  List pages on which this word appears.');
  900.   writeln('  ? -  Question this word: list pages on which it appears.');
  901.   writeln('  I -  Index this word: list pages on which it appears.');
  902.  
  903.   for code := 1 to nfiles do      {start main loop through temporary files.}
  904.   begin
  905.     BuildTree(root, code);  {Get the part of master wordlist starting with
  906.                code from the file InIndex, and build it into a binary tree.}
  907.     reset(RefFile[code]);
  908.     for i := 1 to outcount[code] do
  909.     begin
  910.       Process(RefFile[code]^);
  911.                      {use new words from RefFile[code] to update the tree.}
  912.       get( RefFile[code] )
  913.     end;
  914.  
  915.     OutputTree(root)
  916.                    {write the contents of the tree into file NewIndex.}
  917.   end                                    {main loop on temporary files}
  918. end;                                          {procedure ClassifyWords}
  919.  
  920. {end of all procedures}
  921.  
  922.  
  923.  
  924. begin                                                    {main program}
  925.   SetTimer;
  926.   SplitWords;                                                 {Phase 1}
  927.   writeln('Time in first phase is ', ElapsedTime:7:1, '   seconds.');
  928.   writeln;
  929.  
  930.   ClassifyWords;                                              {Phase 2}
  931.   writeln('Time in second phase is', ElapsedTime:7:1, '  seconds.');
  932.  
  933.   writeln;
  934.   writeln('Processing of input document ', intextname, '  is complete.');
  935.   writeln('Total time in program was ', TotalTime:7:1, '   seconds.')
  936. end.
  937.  
  938.